package ipebg.mzk.common.core.dao.impl;

import ipebg.mzk.common.core.dao.IDBDao;
import ipebg.mzk.common.util.HqlUtil;
import ipebg.mzk.common.util.Page;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.hibernate.stat.Statistics;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate4.SessionFactoryUtils;

/**
 * 
 * @author: xiaoqiao.liu
 * @date: 2016/9/27 *
 * @param <T>
 */
public class DBDao<T> implements IDBDao {

	private static final Log log = LogFactory.getLog(DBDao.class);

	@Autowired
	private SessionFactory sessionFactory;

	protected Class<T> clazz;

	public DBDao() {
	}

	// constants
	public DBDao(Class<T> clazz) {
		this.clazz = clazz;
	}

	protected Query createQuery(String hql, Object... values) {
		Query query = this.getSession().createQuery(hql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				query.setParameter(i, values[i]);
			}
		}
		return query;
	}

	protected Query createQuery(String hql, Map map) {
		Query query = this.getSession().createQuery(hql);
		if (map != null) {
			query.setProperties(map);
		}
		return query;
	}

	protected Query createSqlQuery(String sql, Object... values) {
		Query query = this.getSession().createSQLQuery(sql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				query.setParameter(i, values[i]);
			}
		}
		return query;
	}

	protected Query createSQLQuery(String sql, Map map) {
		Query query = this.getSession().createSQLQuery(sql);
		if (map != null) {
			query.setProperties(map);
		}
		return query;
	}

	/**
	 * 获取session，用于某些特殊操作
	 * 
	 * @return
	 */
	public Session getSession() {
		return sessionFactory.getCurrentSession();
	}

	public Transaction getTransaction() {
		return getSession().beginTransaction();
	}

	public void commitTransaction(Transaction tx) {
		tx.commit();
	}

	public void update(T persistentInstance) {
		log.debug("updating " + this.clazz.getName() + " instance");
		try {
			getSession().update(persistentInstance);
			log.debug("update successful");
		} catch (RuntimeException re) {
			log.error("update failed", re);
			throw re;
		}
	}

	/**
	 * 保存实例
	 * 
	 * @param transientInstance
	 */
	public void save(T transientInstance) throws Exception {
		try {
			getSession().save(transientInstance);
			log.debug("save successful");
		} catch (RuntimeException re) {
			log.error("save failed", re);
			throw re;
		}
	}

	/**
	 * 保存实例
	 * 
	 * @param transientInstance
	 */
	public void saveOrUpdate(T transientInstance) throws Exception {
		try {
			getSession().saveOrUpdate(transientInstance);
			log.debug("save successful");
		} catch (RuntimeException re) {
			log.error("save failed", re);
			throw re;
		}
	}

	public void batchSave(List<T> entityList) throws Exception {
		Session session = getSession();
		for (int i = 0; i < entityList.size(); i++) {
			session.save(entityList.get(i));
			 if (i % 20 == 0) {
			 session.flush();
			 session.clear();
			 }
		}
		 session.flush();
		 session.clear();
	}

	/**
	 * 删除实例
	 * 
	 * @param persistentInstance
	 */
	public void delete(T persistentInstance) throws Exception {
		log.debug("deleting " + clazz.getName() + " instance");
		try {
			getSession().delete(persistentInstance);
			log.debug("delete successful");
		} catch (RuntimeException re) {
			log.error("delete failed", re);
			throw re;
		}
	}

	public void deleteAll(Collection entities) throws Exception {
		try {
			Iterator iterator = entities.iterator();
			while (iterator.hasNext()) {
				Object entity = iterator.next();
				getSession().delete(entity);
			}
		} catch (Exception e) {
			throw e;
		}
	}

	/**
	 * 删除实例
	 * 
	 * @param persistentInstance
	 */
	public void batchDelete(List<T> entityList) {
		log.debug("deleting " + clazz.getName() + " instance");
		try {
			Session session = getSession();
			for (int i = 0; i < entityList.size(); i++) {
				if(entityList.get(i)!=null)
				session.delete(entityList.get(i));
			}
			session.flush();
			 session.clear();
			log.debug("deleteAll successful");
		} catch (RuntimeException re) {
			log.error("batchDelete failed", re);
			throw re;
		}
	}

	/**
	 * 原生SQL 操作，能不用盡量不用，且不要放在事物處理中使用
	 * 
	 * @param persistentInstance
	 */
	public void executeSql(String sql) {
		log.debug("execute sql: " + sql);
		Session session = getSession();
		try {
			session.createSQLQuery(sql).executeUpdate();
			log.debug("executeSql successful");
		} catch (RuntimeException re) {
			log.error("execute failed", re);
			throw re;
		}
	}

	/**
	 * 根据主键查询
	 * 
	 * @param id
	 * @return
	 */
	public T findById(String id) {
		log.debug("getting Users instance with id: " + id);
		try {
			T instance = (T) getSession().get(clazz, id);
			return instance;
		} catch (RuntimeException re) {
			log.error("get failed", re);
			throw re;
		}
	}

	public <E> E findById(Class<E> cls, String id) {
		log.debug("getting Users instance with id: " + id);
		try {
			E instance = (E) getSession().get(cls, id);
			return instance;
		} catch (RuntimeException re) {
			log.error("get failed", re);
			throw re;
		}
	}

	/**
	 * 
	 * description:execute hql or sql query,return the result list
	 * 
	 * @param select
	 *            hql or sql query
	 * @param values
	 *            hql or sql query parameter
	 * @return the result list
	 */
	@SuppressWarnings("rawtypes")
	public List queryForList(String select, Object[] values) throws DataAccessException {
		Query query = createQuery(select, values);
		return query.list();
	}

	public List<T> findByProperty(Class<T> cls, String[] property, Object[] values) {
		List list = null;
		Criteria criteria = this.getSession().createCriteria(cls);
		if (property != null && values != null) {
			for (int i = 0, len = property.length; i < len; i++) {
				criteria.add(Restrictions.eq(property[i], values[i]));
			}
			list = criteria.list();
		}
		return list;
	}

	public List<T> findByProperty(Class<T> cls, String[] property, Object[] values, Order[] orders) {
		List list = null;
		Criteria criteria = this.getSession().createCriteria(cls);
		if (property != null && values != null) {
			for (int i = 0, len = property.length; i < len; i++) {
				criteria.add(Restrictions.eq(property[i], values[i]));
			}
		}

		if (orders != null && orders.length > 0) {
			for (int i = 0, len = orders.length; i < len; i++) {
				criteria.addOrder(orders[i]);
			}
		}

		list = criteria.list();
		return list;
	}

	public List<String> findDistinct(Class<T> cls, String fieldName, String[] property, Object[] values) {
		Criteria criteria = this.getSession().createCriteria(cls);
		if (property != null && values != null) {
			for (int i = 0, len = property.length; i < len; i++) {
				criteria.add(Restrictions.eq(property[i], values[i]));
			}

		}
		return criteria.setProjection(Projections.distinct(Property.forName(fieldName))).list();
	}

	/**
	 * 查找所有实体
	 * 
	 * @param orderStrings
	 *            排序子句，形式如：new String[]{"userName asc","password desc"}
	 *            注意userName是实体类的属性名。
	 * @return List
	 */
	@SuppressWarnings("rawtypes")
	public List findAll(String[] orderStrings) {
		try {
			StringBuffer queryString = new StringBuffer("from " + clazz.getName() + " as model ");
			if ((orderStrings != null) && (orderStrings.length > 0)) {
				// orderString is like "userName asc/desc"
				queryString.append("order by ");
				for (int i = 0; i < orderStrings.length; i++) {
					if (i > 0) {
						queryString.append(",");
					}
					queryString.append(" model.");
					queryString.append(orderStrings[i]);
				}
			}
			List list = createQuery(queryString.toString()).list();
			return list;
		} catch (RuntimeException re) {
			log.error("find all failed", re);
			throw re;
		}
	}

	/**
	 * 根据实体实例查询。
	 * 
	 * @param instance
	 * @param order
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public List findByExample(T instance, Order[] orders) {
		try {
			Criteria dc = getSession().createCriteria(clazz);
			if ((orders != null) && (orders.length > 0)) {
				for (int i = 0; i < orders.length; i++)
					dc.addOrder(orders[i]);
			}

			List results = dc.list();
			log.debug("find by example successful, result size: " + results.size());
			return results;
		} catch (RuntimeException re) {
			log.error("find by example failed", re);
			throw re;
		}
	}

	/**
	 * 原生sql查詢
	 * 
	 * @param selectSql
	 * @param resultValues
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public List findBySQL(final String selectSql, Object... params) {
		Query query = getSession().createSQLQuery(selectSql);
		if (params != null) {
			for (int i = 0; i < params.length; i++) {
				query.setParameter(i, params[i]);
			}
		}
		return query.list();
	}

	/**
	 * 根据示例查询。 find the entity by example condition. userage: public List
	 * findEntitysByExample(Entity entity) { Example exmapleEntity =
	 * Example.create(entity).ignoreCase().enableLike(MatchMode.ANYWHERE);
	 * return findByExample(exmapleEntity); }
	 * 
	 * Order.asc("userId");
	 * 
	 * @param example
	 *            ， 构建好的示例。
	 * @param orders
	 *            ，排序参数。
	 * @return List
	 */
	@SuppressWarnings("rawtypes")
	public List findByExample(Example example, Order[] orders) {
		try {
			Criteria dc = getSession().createCriteria(clazz);
			dc.add(example);
			if ((orders != null) && (orders.length > 0)) {
				for (int i = 0; i < orders.length; i++)
					dc.addOrder(orders[i]);
			}

			List results = dc.list();
			log.debug("find by example successful, result size: " + results.size());
			return results;
		} catch (RuntimeException re) {
			log.error("find by example failed", re);
			throw re;
		}
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public long getCount(Map map) {
		Long rowCount = 0L;
		try {
			Criteria criteria = this.getSession().createCriteria(clazz);

			if (map != null) {
				Set<String> keys = map.keySet();
				for (String key : keys) {
					criteria.add(Restrictions.eq(key, map.get(key)));
				}
			}
			rowCount = Long.parseLong(Integer.toString((Integer) criteria.setProjection(Projections.rowCount()).uniqueResult()));
		} catch (RuntimeException re) {
			re.printStackTrace();
			throw re;
		} finally {
		}
		return rowCount;
	}

	public long getCount(String hql, Map<Serializable, Serializable> map) {
		try {
			Query query = createQuery(hql, map);
			Long i = (Long) query.list().get(0);
			return i;
		} catch (RuntimeException re) {
			throw re;
		}
	}

	public int execute(String hql, Object... params) {
		Query q = createQuery(hql);
		for (int i = 0, len = params.length; i < len; i++) {
			q.setParameter(i, params[i]);
		}
		return q.executeUpdate();
	}

	public <E> E findUnique(Class<E> cls, String hql, Object... params) {
		Query query = createQuery(hql, params);
		return (E) query.uniqueResult();
	}

	public void listByPage(final String select, final Object[] values, final Page page) {
		Query query = createQuery(select, values);
		String countSql = "select count(*) from (" + HqlUtil.hql2Sql(getSession(), select) + ")";
		Query countQ = createSqlQuery(countSql, values);
		long rowCount = ((BigDecimal) countQ.uniqueResult()).longValue();
		List list = query.setFirstResult(page.getFirstResult()).setMaxResults(page.getPageSize()).list();
		page.setResult(list);
		page.setTotalCount(rowCount);
	}

	public <E> List<E> queryForList2Entity(Class<E> cls, String sql, Object[] values) throws DataAccessException {
		Query query = createQuery(sql, values);
		return query.setResultTransformer(Transformers.aliasToBean(cls)).list();
	}

	public <E> List<E> queryForList2Map(String sql, Object[] values) throws DataAccessException {
		Query query = createQuery(sql, values);
		return query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list();
	}

	public <E> List<E> sqlQueryForList2Entity(Class<E> cls, String sql, Object[] values) {
		Query query = createSqlQuery(sql, values);
		return query.setResultTransformer(Transformers.aliasToBean(cls)).list();
	}

	/*-------------Hibernate statistic----------------------*/
	/**
	 * Get secondLevelCache
	 */
	public Map getSecondLevelCache(String regionName) throws Exception {
		Map cacheEntities = null;
		try {
			cacheEntities = getSession().getSessionFactory().getStatistics().getSecondLevelCacheStatistics(regionName).getEntries();
		} catch (Exception e) {
			throw e;
		}
		return cacheEntities;
	}

	public double getQueryCacheHitCount() {
		double queryCacheHitCount = 0;
		try {
			Statistics stats = getSession().getSessionFactory().getStatistics();
			queryCacheHitCount = stats.getQueryCacheHitCount();
		} catch (Exception e) {
			log.error("getQueryCacheHitCount failed," + e.getMessage());
		}
		return queryCacheHitCount;
	}
	
	public double getQueryCacheMissCount() {
		double queryCacheMissCount = 0;
		try {
			Statistics stats = getSession().getSessionFactory().getStatistics();
			queryCacheMissCount = stats.getQueryCacheMissCount();
		} catch (Exception e) {
			log.error("getQueryCacheHitCount failed," + e.getMessage());
		}
		return queryCacheMissCount;
	}
	
	public String[] getEntityNames(){
		String[] entitys=null;
		try {
			Statistics stats = getSession().getSessionFactory().getStatistics();
			entitys = stats.getEntityNames();
		} catch (Exception e) {
			log.error("getEntityNames failed," + e.getMessage());
		}
		return entitys;
	}
	
	public String[] getSecondLevelCacheRegionNames(){
		String[] cacheRegionNames=null;
		try {
			Statistics stats = getSession().getSessionFactory().getStatistics();
			cacheRegionNames = stats.getSecondLevelCacheRegionNames();
		} catch (Exception e) {
			log.error("getSecondLevelCacheRegionNames failed," + e.getMessage());
		}
		return cacheRegionNames;
	}	
	
	public DataSource getDataSource() {
		return SessionFactoryUtils.getDataSource(sessionFactory);
	}
}
